{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "import xalpha as xa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 网格回测\n",
    "\n",
    "在这一笔记中，我们将展示如何使用xalpha进行网格回测。网格回测是一种简单的策略，它在价格波动的时候，以固定的间隔买入或卖出，从而在价格波动中获利。我们主要考虑网格间距和比例等不同的超参对于真实收益的影响。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_settings(\n",
    "    high_price, low_price, price_ratio, amount_ratio, remain_ratio=None\n",
    "):\n",
    "    \"\"\"\n",
    "    generate price and amount list for grid policy\n",
    "    :param high_price: float, the highest price\n",
    "    :param low_price: float, the lowest price\n",
    "    :param price_ratio: float, the ratio between two adjacent prices\n",
    "    :param amount_ratio: float, the ratio between two adjacent amounts\n",
    "    :param remain_ratio: float, the ratio of the remaining amount to sell (留利润)\n",
    "    \"\"\"\n",
    "    if remain_ratio is None:\n",
    "        remain_ratio = price_ratio\n",
    "    prices = [high_price]\n",
    "    while prices[-1] > low_price:\n",
    "        prices.append(round(prices[-1] * price_ratio, 3))\n",
    "    inamount = [10000]\n",
    "    for _ in range(len(prices) - 2):\n",
    "        inamount.append(inamount[-1] * amount_ratio)\n",
    "    outamount = [10000 * remain_ratio]\n",
    "    for i in range(len(prices) - 2):\n",
    "        outamount.append(outamount[-1] * amount_ratio)\n",
    "\n",
    "    return prices, inamount, outamount"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([0.9,\n",
       "  0.855,\n",
       "  0.812,\n",
       "  0.771,\n",
       "  0.732,\n",
       "  0.695,\n",
       "  0.66,\n",
       "  0.627,\n",
       "  0.596,\n",
       "  0.566,\n",
       "  0.538,\n",
       "  0.511,\n",
       "  0.485,\n",
       "  0.461],\n",
       " [10000,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0,\n",
       "  10000.0],\n",
       " [9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0,\n",
       "  9500.0])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 对于传媒指数基金，我们可以生成如下的网格策略\n",
    "\n",
    "prices, inamount, outamount = generate_settings(0.9, 0.48, 0.95, 1.0)\n",
    "\n",
    "# 返回的三个列表分别是价格列表，买入金额列表，卖出金额列表\n",
    "prices, inamount, outamount"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 现在我们开始测试这个策略\n",
    "\n",
    "bt = xa.backtest.Grid(\n",
    "    start=\"20240101\",\n",
    "    end=\"20241231\",\n",
    "    code=\"SH512980\",\n",
    "    prices=prices,\n",
    "    inamount=inamount,\n",
    "    outamount=outamount,\n",
    ")\n",
    "bt.backtest()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>基金名称</th>\n",
       "      <th>基金代码</th>\n",
       "      <th>当日净值</th>\n",
       "      <th>单位成本</th>\n",
       "      <th>持有份额</th>\n",
       "      <th>基金现值</th>\n",
       "      <th>基金总申购</th>\n",
       "      <th>历史最大占用</th>\n",
       "      <th>基金持有成本</th>\n",
       "      <th>基金分红与赎回</th>\n",
       "      <th>换手率</th>\n",
       "      <th>基金收益总额</th>\n",
       "      <th>投资收益率</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>传媒ETF</td>\n",
       "      <td>SH512980</td>\n",
       "      <td>0.774</td>\n",
       "      <td>0.4355</td>\n",
       "      <td>33000.0</td>\n",
       "      <td>25542.0</td>\n",
       "      <td>189970.0</td>\n",
       "      <td>60901.0</td>\n",
       "      <td>14372.0</td>\n",
       "      <td>175598.0</td>\n",
       "      <td>3.009575</td>\n",
       "      <td>11170.0</td>\n",
       "      <td>18.3412</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>总计</td>\n",
       "      <td>total</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>25542.0</td>\n",
       "      <td>189970.0</td>\n",
       "      <td>60885.5</td>\n",
       "      <td>14372.0</td>\n",
       "      <td>175598.0</td>\n",
       "      <td>2.501956</td>\n",
       "      <td>11170.0</td>\n",
       "      <td>18.3459</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    基金名称      基金代码   当日净值    单位成本     持有份额     基金现值     基金总申购   历史最大占用  \\\n",
       "0  传媒ETF  SH512980  0.774  0.4355  33000.0  25542.0  189970.0  60901.0   \n",
       "1     总计     total    NaN     NaN      NaN  25542.0  189970.0  60885.5   \n",
       "\n",
       "    基金持有成本   基金分红与赎回       换手率   基金收益总额    投资收益率  \n",
       "0  14372.0  175598.0  3.009575  11170.0  18.3412  \n",
       "1  14372.0  175598.0  2.501956  11170.0  18.3459  "
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 生成回测持仓报告\n",
    "\n",
    "bt.get_current_mul().combsummary(\"20241231\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "18.3412"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bt.get_current_mul().combsummary(\"20241231\")[\"投资收益率\"].iloc[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.297174042371061"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bt.get_current_mul().xirrrate(\"20241231\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 现在我们进行不同网格超参策略的评估\n",
    "\n",
    "\n",
    "def evaluate(\n",
    "    code,\n",
    "    high_price,\n",
    "    low_price,\n",
    "    price_ratio,\n",
    "    amount_ratio,\n",
    "    remain_ratio=None,\n",
    "    start=\"20240101\",\n",
    "    end=\"20241231\",\n",
    "):\n",
    "    prices, inamount, outamount = generate_settings(\n",
    "        high_price, low_price, price_ratio, amount_ratio, remain_ratio\n",
    "    )\n",
    "    bt = xa.backtest.Grid(\n",
    "        start=start,\n",
    "        end=end,\n",
    "        code=code,\n",
    "        prices=prices,\n",
    "        inamount=inamount,\n",
    "        outamount=outamount,\n",
    "    )\n",
    "    bt.backtest()\n",
    "    mul = bt.get_current_mul()\n",
    "    return round(mul.combsummary(\"20241231\")[\"投资收益率\"].iloc[0], 2), round(\n",
    "        mul.xirrrate(\"20241231\") * 100, 2\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.99 (16.14, 28.13)\n",
      "0.98 (18.9, 32.35)\n",
      "0.97 (18.95, 32.2)\n",
      "0.96 (18.3, 29.0)\n",
      "0.95 (18.34, 29.72)\n",
      "0.94 (17.29, 26.51)\n",
      "0.93 (17.71, 27.08)\n",
      "0.92 (19.89, 29.47)\n",
      "0.91 (20.14, 31.86)\n",
      "0.9 (21.33, 32.1)\n",
      "0.85 (19.36, 25.37)\n",
      "0.8 (28.06, 42.65)\n"
     ]
    }
   ],
   "source": [
    "# 不同网格间距的收益\n",
    "for d in [0.99, 0.98, 0.97, 0.96, 0.95, 0.94, 0.93, 0.92, 0.91, 0.9, 0.85, 0.8]:\n",
    "    print(d, evaluate(\"SH512980\", 0.9, 0.48, d, 1))\n",
    "\n",
    "# 我们看到不同网格间距对收益的影响不是特别大并且不单调"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.99 (41.91, 50.1)\n",
      "0.98 (46.33, 52.86)\n",
      "0.97 (47.62, 53.2)\n",
      "0.96 (46.91, 48.82)\n",
      "0.95 (45.88, 48.89)\n",
      "0.94 (43.47, 43.61)\n",
      "0.93 (42.82, 43.3)\n",
      "0.92 (47.51, 46.02)\n",
      "0.91 (47.99, 48.62)\n",
      "0.9 (50.35, 49.41)\n",
      "0.85 (52.6, 43.4)\n",
      "0.8 (51.83, 55.55)\n",
      "0.99 (15.46, 42.17)\n",
      "0.98 (18.75, 39.27)\n",
      "0.97 (18.82, 36.44)\n",
      "0.96 (18.73, 32.0)\n",
      "0.95 (18.83, 32.51)\n",
      "0.94 (17.54, 28.09)\n",
      "0.93 (18.1, 28.71)\n",
      "0.92 (20.3, 30.94)\n",
      "0.91 (20.25, 33.14)\n",
      "0.9 (21.68, 33.4)\n",
      "0.85 (19.74, 26.07)\n",
      "0.8 (28.61, 44.12)\n"
     ]
    }
   ],
   "source": [
    "# 我们测试这一结果相对其他超参变化的鲁棒性，如回测时间段, 每网买入量的梯度等\n",
    "\n",
    "for d in [0.99, 0.98, 0.97, 0.96, 0.95, 0.94, 0.93, 0.92, 0.91, 0.9, 0.85, 0.8]:\n",
    "    print(d, evaluate(\"SH512980\", 0.9, 0.48, d, 1, start=\"20230101\", end=\"20241231\"))\n",
    "\n",
    "for d in [0.99, 0.98, 0.97, 0.96, 0.95, 0.94, 0.93, 0.92, 0.91, 0.9, 0.85, 0.8]:\n",
    "    print(d, evaluate(\"SH512980\", 0.9, 0.48, d, 1.05))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.99 (16.91, 29.01)\n",
      "0.98 (19.38, 31.98)\n",
      "0.97 (18.87, 31.4)\n",
      "0.96 (19.45, 31.25)\n",
      "0.95 (19.28, 30.87)\n",
      "0.94 (20.71, 33.25)\n",
      "0.93 (20.57, 31.19)\n",
      "0.92 (21.32, 32.06)\n",
      "0.91 (23.89, 36.36)\n",
      "0.9 (21.01, 33.48)\n",
      "0.85 (25.83, 36.22)\n",
      "0.8 (23.05, 26.23)\n"
     ]
    }
   ],
   "source": [
    "# 换个信息技术的标的\n",
    "\n",
    "for d in [0.99, 0.98, 0.97, 0.96, 0.95, 0.94, 0.93, 0.92, 0.91, 0.9, 0.85, 0.8]:\n",
    "    print(d, evaluate(\"SZ159939\", 0.72, 0.35, d, 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 更合理的封闭系统收益率评估, 考虑剩余现金的收益\n",
    "\n",
    "\n",
    "def evaluate_fix(\n",
    "    code,\n",
    "    high_price,\n",
    "    low_price,\n",
    "    price_ratio,\n",
    "    amount_ratio,\n",
    "    remain_ratio=None,\n",
    "    start=\"20240101\",\n",
    "    end=\"20241231\",\n",
    "):\n",
    "    prices, inamount, outamount = generate_settings(\n",
    "        high_price, low_price, price_ratio, amount_ratio, remain_ratio\n",
    "    )\n",
    "    bt = xa.backtest.Grid(\n",
    "        start=start,\n",
    "        end=end,\n",
    "        code=code,\n",
    "        prices=prices,\n",
    "        inamount=inamount,\n",
    "        outamount=outamount,\n",
    "    )\n",
    "    bt.backtest()\n",
    "    mul = bt.get_current_mul()\n",
    "    totmoney = mul.combsummary(\"20241231\")[\"历史最大占用\"].iloc[0]\n",
    "    mulfix = bt.get_current_mulfix(totmoney=totmoney + 1.0)\n",
    "    return mulfix.combsummary(\"20241231\")[\"投资收益率\"].iloc[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "mulfix = evaluate_fix(\"SZ159939\", 0.72, 0.35, 0.95, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.99 18.4835\n",
      "0.98 20.8728\n",
      "0.97 20.3825\n",
      "0.96 20.8855\n",
      "0.95 20.6979\n",
      "0.94 22.1393\n",
      "0.93 21.8617\n",
      "0.92 22.5938\n",
      "0.91 25.1978\n",
      "0.9 22.4241\n",
      "0.85 26.9288\n",
      "0.8 23.5061\n"
     ]
    }
   ],
   "source": [
    "for d in [0.99, 0.98, 0.97, 0.96, 0.95, 0.94, 0.93, 0.92, 0.91, 0.9, 0.85, 0.8]:\n",
    "    print(d, evaluate_fix(\"SZ159939\", 0.72, 0.35, d, 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "结论就是不同的网格宽度对应的收益率大体类似，甚至更宽的网格收益情况更好，因此选取较低频的网格宽度即可。\n",
    "\n",
    "下面我们评估越便宜买的越多的网格策略是否有超额收益。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0 22.1393\n",
      "1.02 21.8901\n",
      "1.04 21.2032\n",
      "1.06 20.5496\n",
      "1.08 17.4569\n",
      "1.1 21.174\n",
      "1.15 19.3504\n",
      "1.2 20.0333\n"
     ]
    }
   ],
   "source": [
    "for d in [1.0, 1.02, 1.04, 1.06, 1.08, 1.1, 1.15, 1.2]:\n",
    "    print(d, evaluate_fix(\"SZ159939\", 0.72, 0.35, 0.94, d))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "整体结论可能也是区别不大，对于网格具体超参调优可以根据自己的投资标的和策略自行回测，但警惕过拟合陷阱，可能本文的主要结论就是这些超参的调整对于最后的收益都区别不是特别大。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "xa310",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.16"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
